Skip to content

fix(automation): a flow run's answer names the read-only fields its write steps dropped (#32861) - #33007

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-1wu3fs
Nov 13, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-1wu3fs

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Part of #32861

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

What changes

Measured first (origin/main 04c35f0). The update_record / create_record step already reports a strip: warnings (sentences) on the step, droppedFields (bare names) on its output, both on the step log since #3407. Traced to the caller:

Layer Carries the drop?
step log (StepLogEntry.warnings) and run history yes, as sentences
the run's answer (AutomationResult, built at the engine's terminal exits) no: no member for it
dispatchFlowAction (packages/runtime/src/action-execution.ts) relays the result whole
REST envelope (restAnswerOf: deps.success(result)) relays the result whole
MCP run_action (invokeBusinessAction: result) relays the result whole

So the layer that drops it is the engine's run result, and that is the only place this PR changes behaviour. The click on #32666's shape answered { success: true, flowLabel, summary } with the record still draft (the verify pin below, ablated, reproduces that answer).

The carry.

  • NodeExecutionResult.droppedFields / StepLogEntry.droppedFields (service-internal): the write node hands back the engine's DroppedFieldsEvents it already collected from onFieldsDropped, beside the warnings it already wrote; dag-traversal.ts copies them onto the step, like warnings.
  • run-dropped-fields.ts#dropEventsOf: a fold over the flat step log, like summarizeRun. One event per (object, reason), fields the union in first-seen order. Returns undefined when nothing was dropped.
  • The three terminal-success exits spread withDroppedFields(steps): execute() (engine.ts), a retried attempt (dag-traversal.ts), and a resumed run (run-resume.ts, over the whole run, so a drop before a screen pause reaches the answer the run finally gives).
  • subflow node: a child that completes without pausing hands its droppedFields up onto the parent's step, so the parent's answer carries them. A child that paused, and a map item's run, do not roll up yet (automation: a flow's answer carries the dropped-field fact of its map items and of a subflow child that paused and resumed (delegated half of #32861) #33019); both published sentences (the TriggerFlowResponseSchema describe and the AutomationResult jsdoc) state that rule as built, and that a field named means at least one write dropped it.

The contract. AutomationResult.droppedFields?: DroppedFieldsEvent[] (packages/spec/src/contracts/automation-service.ts) and its mirror TriggerFlowResponseSchema.data.droppedFields (z.array(DroppedFieldsEventSchema).optional()); the compile-time guard TriggerFlowDataMatchesContract binds the two. The element is the data engine's own DroppedFieldsEventSchema, so no new vocabulary. The regenerated reference page content/docs/references/api/automation-api.mdx gains the one row.

Unchanged. success, status, summary, the step warnings and the step output's droppedFields (names) all read as before. A refused run, a failed run and a paused answer carry no droppedFields.

Tests

Ablation (the carry stripped: withDroppedFields returns an empty object)

Committed first (e0e5b6dd), then mutated through scripts/ablation-replace.mjs (anchor return droppedFields ? { droppedFields } : {}; x1 -> x0, blob 127c6147 -> 172b361b), service-automation rebuilt, ablation-dist-preflight --absent: marker absent from all 6 built files.

Restore: blob after restore 127c6147 == HEAD, git diff HEAD empty; rebuilt, preflight marker present in 2 built files; verify Tests 2 passed (2).

Single-exit ablation of the retried-attempt exit (db40c4a8, the spread removed from dag-traversal.ts executeWithoutRetry alone; anchor x1 -> x0, blob aca7bf1a -> 7d968703): run-dropped-fields.test.ts Tests 1 failed | 7 passed (8), the retry case alone red. Restored: blob == HEAD aca7bf1a, git diff HEAD empty.

Acceptance notes

Verification

Final head 3203683d. Contract review PASS on 5cee1bbe (record 6843734657); this round executes seat rulings 6843826357 item 2, and only the affected gates were re-run on it (below). Package suites, typecheck and the full derived set are from 5cee1bbe.

Package tests and typecheck
  • pnpm --filter @objectstack/service-automation test: Test Files 279 passed (279), Tests 3818 passed (3818) (on 4968c9c9; 5cee1bbe adds only collection-time imports to two test files outside this package).
  • pnpm --filter @objectstack/spec test: Test Files 837 passed (837), Tests 26772 passed | 1 todo (26773).
  • pnpm --filter @objectstack/runtime exec vitest run --project local src/action-flow-dropped-fields-relay.test.ts: Tests 4 passed (4); pnpm --filter @objectstack/verify exec vitest run src/action-flow-dropped-fields.test.ts: Tests 2 passed (2) (both on 5cee1bbe).
  • pnpm --filter @objectstack/service-automation --filter @objectstack/runtime --filter @objectstack/verify --filter @objectstack/spec typecheck: exit 0.
  • pnpm --filter @objectstack/spec check:generated: All 14 generated artifacts are up to date (after --fix regenerated content/docs/references/**).
Round 2 on `3203683d`: affected gates and tests
  • run-dropped-fields.test.ts: Tests 8 passed (8).
  • spec src/api/automation-api.zod.test.ts + src/contracts/: Test Files 52 passed (52), Tests 551 passed (551); spec tsc --noEmit exit 0.
  • pnpm --filter @objectstack/spec check:generated: All 14 generated artifacts are up to date (the reference page regenerated twice in this round).
  • The derivation is unchanged from 5cee1bbe (same 119 commands). The 60 that read a changeset, packages/spec, docs, issue citations, NUL bytes or test sources were re-run: 60 exit 0. The first pass was red on pnpm check:doc-authoring (an issue id inside a .describe()); 3203683d moves it to the adjacent comment and the re-run is green.
Round 1 on `5cee1bbe`: derived gates (`dispatch-gates.mjs --commands`, 119) and lint

Run on head 5cee1bbe, every command's exit captured before any pipe; node scripts/pm/dispatch-gates.mjs --ran gates-ran-5cee1bbe.txt: ✓ dispatch-gates --ran: 119 derived famil(ies) accounted for — 118 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).

  • 118 exit 0.
  • NOT MEASURED: node scripts/check-plugin-teardown-shape.mjs --self-test, exit 3, prerequisite not met (the shallow clone cannot reach its pinned fixture commit; it grades the checker, not this diff). CI runs it.
  • The first run (head 4968c9c9) was red on pnpm check:test-source-alias (the two new door tests reached the harness's dynamic imports inside a clocked case); 5cee1bbe adds the module-top imports the gate prescribes, and the re-run above is green.

pnpm lint (eslint . --no-inline-config) on 5cee1bbe: exit 0.


Generated by Claude Code

… dropped

A record button whose flow wrote a read-only field answered a plain
success: the step's drop events stopped at the run log. The terminal
answer now carries them as AutomationResult.droppedFields (the data
engine's own DroppedFieldsEvent, coalesced, omit-when-empty), mirrored
on TriggerFlowResponseSchema; a subflow child's drops ride up.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cwHRkD8EXD38KeqzFh
…docs and changeset

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cwHRkD8EXD38KeqzFh
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 13, 2026
@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-automation, @objectstack/spec, touching 16 documentable anchor(s).

26 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json e728d7a122fd68e1a3b1a4df27b07b4b13b2295a.

⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • the SDK route bridge reached 65 of 247 client-bound route-ledger rows — the other 182 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 182: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 123 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 171 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e728d7a122fd68e1a3b1a4df27b07b4b13b2295a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 8021368beaa6358fdd26c752ebf8f1cf8058861c — the merge of head 3203683dc66c57c0425d74ab484b534ee7e07e4a into base e728d7a122fd68e1a3b1a4df27b07b4b13b2295a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 8021368beaa6358fdd26c752ebf8f1cf8058861c && git checkout 8021368beaa6358fdd26c752ebf8f1cf8058861c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e728d7a122fd68e1a3b1a4df27b07b4b13b2295a 3203683dc66c57c0425d74ab484b534ee7e07e4a && git checkout -B drift-repro e728d7a122fd68e1a3b1a4df27b07b4b13b2295a && git merge --no-ff 3203683dc66c57c0425d74ab484b534ee7e07e4a

node scripts/docs-audit/affected-docs.mjs --json e728d7a122fd68e1a3b1a4df27b07b4b13b2295a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs e728d7a122fd68e1a3b1a4df27b07b4b13b2295a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J

Contract-review-claim: session_016WV4cwqTK71kjQzHae8T5J · head 5cee1bb · 2026-11-13T03:58Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5cee1bbe5f70167bffccb2e2c0004fcb92d80e97
Local-runs: none
Scope: packages/spec/src/api/automation-api.zod.ts, packages/spec/src/contracts/automation-service.ts + packages/services/service-automation/src/run-dropped-fields.ts, packages/services/service-automation/src/engine.ts, packages/services/service-automation/src/dag-traversal.ts, packages/services/service-automation/src/run-resume.ts, packages/services/service-automation/src/builtin/crud-nodes.ts, packages/services/service-automation/src/builtin/subflow-node.ts

Widened, read and graded because the brief asked: the four test files, .changeset/32861-flow-answer-dropped-fields.md, and the regenerated content/docs/references/api/automation-api.mdx. Read at this head, never reviewed: builtin/map-node.ts, run-history.ts, suspended-run-store.ts, packages/runtime/src/domains/automation.ts, packages/runtime/src/action-execution.ts, packages/runtime/src/route-ledger.ts, packages/rest/src/rest-route-ledger.ts, and spec data/data-engine.zod.ts, automation/execution.zod.ts, api/protocol.zod.ts.

Check-runs on this head, read 2026-11-13T04:12Z: 32 success, 10 skipped, 24 in_progress (Test Core 1 to 8, Test UI 1 to 16), 0 failed. Every type-check, lint, build, Check Changeset, Spec property liveness, PR-body guards and Governed Surface Queue Guard run is green. The test shards had not concluded, so "every check green" is the seat's to re-read before queueing.

① Derived judgments

  1. TriggerFlowResponseSchema.data.droppedFields is a widening: right. It is z.array(DroppedFieldsEventSchema).optional() on a strip-mode object, so every payload accepted before is still accepted. The one theoretical exception is a payload that already carried a top-level droppedFields of another shape, which the schema used to strip silently. No producer on main emits one; the step's output.droppedFields sits inside z.unknown(). The doors relay without parsing, so nothing is refused at run time.
  2. AutomationResult.droppedFields? is a widening: right. TriggerFlowDataMatchesContract in automation-api.zod.test.ts binds schema and contract member for member. An IAutomationService implementer need not set it.
  3. NodeExecutionResult.droppedFields? and StepLogEntry.droppedFields? are widenings: right. The PR body calls them service-internal. They are on the exported executor contract and on the persisted step log; the same declaration and the service-automation changeset entry cover them. The wire consequence is nit N2.
  4. Omit-when-empty: right. withDroppedFields returns an empty object unless the fold found an event with at least one field. It is pinned by 'droppedFields' in res === false at the engine, at both action entrances and on the real kernel.
  5. Refused, failed, paused, never-dispatched and skip exits carry none: right. finishRefusedRun, both status: 'failed' exits, the exhausted-retry exit, the paused returns and the two skip returns are untouched.
  6. Vocabulary: none new, right. The element is the existing DroppedFieldsEventSchema. CreateDataResponse, UpdateDataResponse, CreateManyDataResponse, the batch results and the import row result already carry it under the same member name. One event per object and reason with the field union is the convention CreateManyDataResponse.droppedFields already states.
  7. Metadata-protocol: none: right. Nothing a metadata author can write changes, and no authorable-surface/ or authorable-defaults/ shard moves.

② Semver level

  • patch on @objectstack/spec and @objectstack/service-automation: right. AGENTS.md Post-Task Checklist item 3 says the level follows the kind alone, none takes patch, and a non-protocol widening ships patch. scripts/check-changeset-no-major.mjs enforces that a declared none grades every entry patch and moves no authorable baseline. Check Changeset is green.
  • Declaration lines: identical. Clause-②: yes (widening) and Metadata-protocol: none are line-initial and the same in the PR body and the changeset.
  • @objectstack/runtime and @objectstack/verify: no entry owed. The diff adds only test files there.
  • should-fix (changeset prose) P1: the changeset names a route that does not exist. It lists POST /api/v1/data/:object/:id/actions/:action as a door that relays the answer. packages/rest/src/rest-route-ledger.ts states that no action-invoke route exists in the data family and that its absence is deliberate (capability-declaration-lifecycle: no shipped resource enforces a package capability server-side (no REST action-invoke route on this build) #7680). The second spelling the dispatcher mounts is POST /api/v1/actions/:object/:action/:recordId (packages/runtime/src/route-ledger.ts). The changeset ships as CHANGELOG.md.
  • should-fix (changeset prose) P2: the subflow sentence is unconditional and the code is not. "a subflow child's drops ride up onto its parent's answer" holds only for a child that completes without pausing. The PR body says so; the changeset does not. See S1.
  • nit (changeset prose): the doors list omits the resume door. POST /api/v1/automation/:name/runs/:runId/resume and the MCP resume_run tool are where "a drop before a pause" is delivered.
  • A fix confined to the changeset does not move a reviewed file.

③ Boundary flags

④ Design judgment

must-fix: none.

What holds, read in the tree

  • automation-api.zod.ts: the member reuses the engine's schema, as the record-save answers do. The describe is true of the run's own write steps sentence by sentence, including stored_expression ("did not store" covers a value replaced by the computed one). The first consumer's parser, isWireDroppedFieldsEntry in ui/packages/data-objectstack, reads this entry shape.
  • automation-service.ts: the jsdoc matches the fold (one event per object and reason, first-seen order, absent and never an empty array).
  • run-dropped-fields.ts: the fold copies fields, so no step or child array is mutated. Map insertion order gives first-seen order. It dedupes by object, reason and field, so a loop, a try_catch retry or a re-entered node cannot double count.
  • engine.ts, dag-traversal.ts, run-resume.ts: the only three status: 'completed' sites in the package each carry the spread. There is no fourth terminal-success exit.
  • Pause and resume: resumeInternal folds run.steps, the array the pause persisted. ObjectStoreSuspendedRunStore writes JSON.stringify(run.steps) whole and reads it back unprojected, so a pre-pause drop survives a durable pause.
  • crud-nodes.ts: the events are the ones the node already collected; output.droppedFields and warnings are byte-unchanged.
  • subflow-node.ts: a child that completed hands up its own fold. A failed or refused child has none. The child's steps live in the child's log, so the parent cannot count them twice.
  • Accepted scenario (card Work 1 and 3, the A runtime-authored flow that writes a readonly field as the user publishes clean; the button runs, reports success and changes nothing (flow-update-readonly-field is not run at the save/publish gate) #32666 shape): pinned at the engine, at both action entrances with a scripted service, and on the real kernel through actions.run (answer names status, record stays draft, note lands). The reported ablation is consistent with the code: the four cases that read an exit go red; the two fold cases and the drop-nothing case do not.

should-fix S1 (both hits, subflow-node.ts): delegation is undeclared and uneven.

  • The same answer's summary credits a child run's writes from a subflow and from each map item, on the synchronous leg and on both resumed legs (creditChildRun).
  • droppedFields rolls up only from a subflow child that completes without pausing. builtin/map-node.ts never reads child.droppedFields. The delegated resume leg and bubbleToParent carry output and summary only.
  • So a parent whose map items each drop status answers summary.acted: N with no droppedFields.
  • The jsdoc says "the run's own write steps", which the subflow roll-up already exceeds. Neither published sentence tells a reader that absence is not proof for a delegated write.
  • Smallest fix here: one sentence on both hits, and the regenerated mdx, stating the rule as built. A subflow child that completes without pausing rolls up; a child that paused, and a map item's run, do not.
  • Then a server-lane card for the roll-up itself (the map node's completion exit and creditChildRun's two callers). Doing the roll-up in this PR is the alternative.
  • Until then console: a record-button flow whose write step dropped a read-only field shows a warning toast naming the fields (console half of #32861) #33009's reader must not treat an absent member as "nothing was dropped" for those shapes.

should-fix S2 (pin): the retried-attempt exit is unpinned.

  • None of the seven cases declares errorHandling.strategy: 'retry'.
  • The ablation cut the shared helper, which reds all three exits together, so it cannot show per-exit coverage.
  • Deleting the spread in executeWithoutRetry leaves every test green. That file's own comments count seven drifts of this method from execute().
  • One case closes it: a flow under strategy: 'retry' whose first attempt fails and whose second completes with a drop.

nit N1: the fold runs after recordLog('completed'), inside the try whose catch reads any throw as a node failure (the #16274 and #15944 class).

  • It cannot throw on well-typed events, and both in-tree producers are well-typed.
  • A host-registered executor returning a malformed droppedFields would turn a completed run into a failed or retried one.
  • Harden in the commit that moves the head for S1 or S2, or carry it on the follow-up card.

nit N2: the step-log member reaches a published wire undeclared.

nit N3: "Set on a terminal success only" can be read to include status: 'refused', which answers success: true. The code sets the member on a completed run only. successMessage uses the same phrase with the same exclusion, so this is an accepted deviation.

nit N4: the union reading is unstated. A name means at least one write dropped it, not every write. CreateManyDataResponse.droppedFields says so in its describe; this one does not. Add it if the describe is touched for S1.

nit N5: the failure-arm copy in executeNode is unreachable today. No executor returns droppedFields beside success: false. Harmless symmetry with warnings.

nit N6: the pause pin runs on InMemorySuspendedRunStore only. The durable round trip holds by reading and is unpinned.

Implemented-by: claude/project-thread-1wu3fs
Reviewed-by: session_016WV4cwqTK71kjQzHae8T5J

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J

Contract-review-claim: session_016WV4cwqTK71kjQzHae8T5J · head 3203683 · 2026-11-13T04:51Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 3203683dc66c57c0425d74ab484b534ee7e07e4a
Local-runs: none
Scope: packages/spec/src/api/automation-api.zod.ts, packages/spec/src/contracts/automation-service.ts + packages/services/service-automation/src/run-dropped-fields.test.ts, .changeset/32861-flow-answer-dropped-fields.md, content/docs/references/api/automation-api.mdx

Delta review of record 6843734657 (PASS on 5cee1bbe, with should-fix S1, S2, P1 and P2 open). The head moved by three linear commits (db40c4a8, cca51fda, 3203683d) with no merge of main; the merge base is still 04c35f09. The five files on the Scope line are the whole delta. Read at this head, never reviewed: builtin/subflow-node.ts, builtin/map-node.ts, run-resume.ts, engine.ts (bubbleToParent, retryExecution), dag-traversal.ts, packages/runtime/src/domains/automation.ts, packages/runtime/src/domains/mcp.ts, packages/runtime/src/route-ledger.ts, packages/runtime/src/dispatcher-plugin.ts, scripts/check-doc-authoring.mjs.

Check-runs on this head, read 2026-11-13T04:56Z: 26 success, 12 skipped, 31 in_progress, 0 failed.

  • Green: every lint job (including Lint · repo & tooling gates, which runs pnpm check:doc-authoring, and Lint · docs & code guards), Type Check · source gates, Build Docs, Check Changeset, PR-body guards, Spec property liveness, Governed Surface Queue Guard.
  • Not concluded: Test Core 1 to 8, Test UI 1 to 16, Build Core, Build & E2E UI, Live E2E UI, and the workspace, UI, consumer-gates and debt-ledger type checks.
  • The shard that runs the new retry pin is among those still running, so its green rests on the dev report (8 of 8) until the seat re-reads.

Check-runs on the round-1 head 5cee1bbe, read 2026-11-13T04:54Z: all concluded, 58 success, 10 skipped, 0 failed. The 24 shards that record 6843734657 left in progress are answered green.

① Derived judgments

  • The delta changes no accept set and no type. The automation-api.zod.ts hunk is one // comment and the describe string; z.array(DroppedFieldsEventSchema).optional() is untouched. The automation-service.ts hunk is jsdoc only; the member's type is untouched.
  • All seven judgments of 6843734657 carry unchanged: widening on schema and contract, the two service-automation members, omit-when-empty, nothing on refused, failed or paused exits, no new vocabulary, Metadata-protocol: none.
  • The describe string is published text (JSON Schema description and the reference page). The change documents the same accept set; it neither widens nor narrows it.

② Semver level

  • patch on both packages: still right. The delta publishes prose only. The declaration lines are unchanged in the changeset and identical in the replaced PR body. Check Changeset is green on this head.
  • P1 closed. The data-family route is gone from the changeset and from the tree. The second door now named, POST /api/v1/actions/:object/:action/:recordId, is mounted in dispatcher-plugin.ts and has a row in route-ledger.ts.
  • P2 closed. The changeset now says a subflow child that completes without pausing rolls up, and that a child that paused and a map item's run do not yet (automation: a flow's answer carries the dropped-field fact of its map items and of a subflow child that paused and resumed (delegated half of #32861) #33019). That is the code; see ④.
  • Doors nit closed. The changeset adds POST /api/v1/automation/:name/runs/:runId/resume and the MCP resume_run tool. Both relay the engine's answer: the REST door ends deps.success(result), and resume_run answers it under result, as run_action does. The trigger and resume routes both have rows in route-ledger.ts.

③ Boundary flags

④ Design judgment

must-fix: none. should-fix: none.

S1 closed. Each new sentence on the two hits is true of the code.

  • "a field named means at least one write dropped it, not every write" (describe, jsdoc, changeset): true. dropEventsOf unions fields across steps under one object-and-reason key. This also closes N4.
  • "a subflow child that completes without pausing rolls its drops up" (describe "here", jsdoc "onto this answer"): true.
    • subflow-node.ts puts a completed child's droppedFields on the parent's step, and the parent's fold reads it.
    • This holds when the parent itself pauses later, because the step is persisted.
    • A failed or refused child's answer carries none.
  • "a child that paused, and a map item's run, do not yet": true.
    • The delegated resume leg and bubbleToParent hand the parent the child's output and summary only.
    • map-node.ts never reads the member, on its synchronous exit or its paused one.
  • "an absent member is not proof that a delegated write dropped nothing": true. The converse it leaves standing also holds: the only built-in nodes that write records are create_record and update_record, and both report.
  • "unlike {@link summary}, which credits all three" (jsdoc): true.
    • The subflow step's rolled metrics credit a child that completed.
    • creditChildRun credits a paused child on the delegated leg; on the up-bubble, bubbleToParent passes the child's summary into the parent's resume.
    • map-node.ts sums each item's summary, and a paused item is credited by the same creditChildRun.
  • The jsdoc's earlier "the run's own write steps" now reads with the delegated paragraph that follows it. The two are consistent.
  • The regenerated mdx row equals the describe string character for character and carries no card id.

S2 closed.

  • The new case runs under errorHandling: { strategy: 'retry', maxRetries: 1, backoffMs: 0 }.
  • Attempt 1 fails at the flaky node inside execute(), so execute()'s own completion exit is never reached. retryExecution returns attempt 2's result from executeWithoutRetry.
  • So res.droppedFields can only come from the spread in dag-traversal.ts. expect(attempts).toBe(2) holds that the ladder ran.
  • The reported single-exit ablation (1 failed, 7 passed of 8, the retry case alone red) is what the code predicts. The restored blob aca7bf1a is this head's blob for dag-traversal.ts.
  • The same errorHandling block is already used in end-node-refused-outcome.test.ts.

The files the delta leaves untouched stand as reviewed.

  • Blob ids are equal between 5cee1bbe and this head for run-dropped-fields.ts, engine.ts, dag-traversal.ts, run-resume.ts, builtin/crud-nodes.ts, builtin/subflow-node.ts, and for builtin/map-node.ts, which the new sentences cite.
  • The three other test files are byte-identical too.
  • Every check on 5cee1bbe has since concluded green.

nit (new, pin): the retry case cannot tell attempt 2's drop from attempt 1's. Both attempts drop the same field, while the title says "from the attempt that completed". The exit is what S2 asked to pin and it is pinned. Accepted as written.

Implemented-by: claude/project-thread-1wu3fs
Reviewed-by: session_016WV4cwqTK71kjQzHae8T5J

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J

ACCEPT · PR #33007 · head 3203683dc66c57c0425d74ab484b534ee7e07e4a · 2026-11-13T05:17Z · Part of #32861

席位核对

  1. Shape: draft PR to main, Part of #32861 alone (seat ruling A, 6843826357: the card closes when console: a record-button flow whose write step dropped a read-only field shows a warning toast naming the fields (console half of #32861) #33009 and automation: a flow's answer carries the dropped-field fact of its map items and of a subflow child that paused and resumed (delegated half of #32861) #33019 land). Path surface (14 files): packages/spec/src/{api/automation-api.zod.ts,contracts/automation-service.ts} + test, packages/services/service-automation/src/** (engine, traversal, resume, crud and subflow nodes, the new fold) + test, a runtime relay test, a verify real-kernel test, content/docs/references/api/automation-api.mdx (regenerated), one changeset. No governed path. Tier S.
  2. Premise measured: the drop stopped at the engine's run result (AutomationResult had no member); dispatchFlowAction, the REST envelope and MCP run_action relay the result whole. The fix adds the member at the three terminal-success exits and the synchronous subflow roll-up.
  3. Gates: check-runs on this head: 60 success, 12 skipped, 0 failed, 0 pending.
  4. Contract review: CONTRACT_REVIEW_TIER PASS 6843734657 on 5cee1bbe; delta 5cee1bbe..3203683d PASS 6844555869 on this head (S1, S2, P1, P2 closed; no must-fix, no should-fix; one new pin nit accepted).
  5. Prose checked sentence by sentence against the diff: the changeset (the member and its mirror; the element is DroppedFieldsEventSchema; terminal success only, absent otherwise; one event per object and reason; a drop before a pause reaches the resumed answer; the union reading; the delegated rule as built with automation: a flow's answer carries the dropped-field fact of its map items and of a subflow child that paused and resumed (delegated half of #32861) #33019; outcome unchanged; the doors list: both actions routes, MCP run_action, the trigger route, the resume route and MCP resume_run) and the regenerated automation-api.mdx row (equal to the describe string) agree with the code the two records read.
  6. Out of scope and carried: console toast → console: a record-button flow whose write step dropped a read-only field shows a warning toast naming the fields (console half of #32861) #33009 (domain:ui); paused-child and map roll-up with the fold hardening → automation: a flow's answer carries the dropped-field fact of its map items and of a subflow child that paused and resumed (delegated half of #32861) #33019; ExecutionStepLogSchema members → spec: declare warnings and droppedFields on ExecutionStepLogSchema (run history serves both undeclared) #33020. One private item went to the security lane, graded there; it does not hold this PR.
  • Landing: Tier S path, Clause-②: yes (widening), Metadata-protocol: none, patch. main not merged since the PR opened (already up to date then); K = 4 (e728d7a122, this head's Test Core affected-set base, to origin/main), under 15. In-flight p1 under the maintainer's rule (「后续只派 p0 p1 和阻塞 p0 p1 的卡」).

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 13, 2026 05:17
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 43371288757 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Dogfood Regression Gate (1/3) — 失败步骤: Boot example apps and exercise real user flows

    @objectstack/dogfood:test:  FAIL   isolated  test/security-catalog-showcase.dogfood.test.ts > showcase, 'single': the security catalog read > permission: lists exactly the declared names
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 job 看)
    @objectstack/dogfood:test:  FAIL   isolated  test/security-catalog-showcase.dogfood.test.ts > showcase, 'single with the Default Organization': the security catalog read > permission: lists exactly th
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 job 看)
    @objectstack/dogfood:test:  FAIL   isolated  test/security-catalog-showcase.dogfood.test.ts > showcase, 'walled': the security catalog read > permission: lists exactly the declared names
      ↳ 失败原因: @objectstack/dogfood:test: AssertionError: expected [ 'admin_full_access', …(18) ] to deeply equal [ 'admin_full_access', …(17) ]
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 17 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J

Merge-queue kick · PR #33007 · head 3203683dc66c57c0425d74ab484b534ee7e07e4a · 2026-11-13T05:45Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants